package panda.natalia.gothread.suspend.instrument;

import static panda.natalia.gothread.suspend.GlobalDefine.*;
import static panda.natalia.gothread.suspend.instrument.GoThreadMethodInstrumentor.ASMAPI;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.JSRInlinerAdapter;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import  panda.natalia.gothread.suspend.instrument.MethodDatabase.SuspendableType;
import  panda.natalia.gothread.suspend.instrument.MethodDatabase.ClassEntry;

import java.util.ArrayList;
import java.util.List;

/**
 * a simple framework of java fiber
 *
 * @Author: scpanda2007
 * @Date: 2019/11/29 11:51
 */
public class InstrumentClass extends ClassVisitor {

    private final MethodDatabase db;
    private final SuspendableClassifier classifier;
    private boolean forceInstrumentation;
    private boolean suspendableInterface;
    private String className;
    private boolean isInterface;
    private ClassEntry classEntry;
    private String sourceName;
    private String sourceDebugInfo;

    private ArrayList<MethodNode> methods;
    private boolean alreadyInstrumented;

    private RuntimeException exception;

    public InstrumentClass(ClassVisitor classVisitor, MethodDatabase db, boolean forceInstrumentation) {
        super(ASMAPI, classVisitor);
        this.db = db;
        this.classifier = db.getClassifier();
        this.forceInstrumentation = forceInstrumentation;
        this.suspendableInterface = false;
    }

    /**
     *
     * Access 访问标志
     *
     * 　　访问标志（access_flages）．这个标志用于识别一些类或者接口层次的访问信息，包括：这个class是类还是接口，是否定义为public类型，是否定义为abstract类型，如果是类的话，否被声明为final等．
     * 　　访问标志及其含义具体如下：
     *
     * 标志名称　	标志值	含义
     * ACC_PUBLIC	0x00 01	是否为Public类型
     * ACC_FINAL	0x00 10	是否被声明为final，只有类可以设置
     * ACC_SUPER	0x00 20	是否允许使用invokespecial字节码指令的新语义．
     * ACC_INTERFACE	0x02 00	标志这是一个接口
     * ACC_ABSTRACT	0x04 00	是否为abstract类型，对于接口或者抽象类来说，次标志值为真，其他类型为假
     * ACC_SYNTHETIC	0x10 00	标志这个类并非由用户代码产生
     * ACC_ANNOTATION	0x20 00	标志这是一个注解
     * ACC_ENUM	0x40 00	标志这是一个枚举
     *
     * @param version
     * @param access
     * @param name
     * @param signature
     * @param superName
     * @param interfaces
     */
    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces){
        this.className = name;
        this.isInterface = (access & Opcodes.ACC_INTERFACE) != 0;

        this.classEntry = db.getOrCreateClassEntry(className, superName);
        classEntry.setInterfaces(interfaces);

        this.forceInstrumentation |= classEntry.requiresInstrumentation();

        if (version < Opcodes.V1_5) version = Opcodes.V1_5; // 版本最低限制

        super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public void visitSource(String source, String debug){
        this.sourceName = source;
        this.sourceDebugInfo = debug;
        super.visitSource(source, debug);
        classEntry.setSourceName(source);
        classEntry.setSourceDebugInfo(debug);
    }

    boolean hasSuspendableMethods() { return methods!=null && !methods.isEmpty();}

    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible){
        if(desc.equals(INSTRUMENTED_DESC))
            this.alreadyInstrumented = true;
        else if(isInterface && desc.equals(SUSPENDABLE_DESC))
            this.suspendableInterface = true;

        return super.visitAnnotation(desc, visible);
    }

    @Override
    public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
        SuspendableType markedSuspendable = null;
        if(suspendableInterface)
            markedSuspendable = SuspendableType.SUSPENDABLE_SUPER;
        if(markedSuspendable == null)
            markedSuspendable = classifier.isSuspendable(db, sourceName, sourceDebugInfo, isInterface, className, classEntry.getSuperName(), classEntry.getInterfaces(), name, desc, signature, exceptions);
        final SuspendableType setSuspendable = classEntry.check(name, desc);

        if(setSuspendable == null)
            classEntry.set(name, desc, markedSuspendable != null ? markedSuspendable : SuspendableType.NON_SUSPENDABLE);

        final SuspendableType suspendableType = max(markedSuspendable, setSuspendable, SuspendableType.NON_SUSPENDABLE);

        if((access & Opcodes.ACC_NATIVE) == 0 && !isYieldMethod(className, name)){
            if(methods == null) methods = new ArrayList<>();
            final MethodNode mn = new MethodNode(access, name, desc, signature, exceptions);

            return new MethodVisitor(ASMAPI, mn) {

                private SuspendableType susp = suspendableType;
                private boolean commited = false;

                private void commit() {
                    if(commited) return;

                    if(db.isDebug()){
                        db.log(LogLevel.INFO, "Method %s#%s%s suspendable: %s (markedSupendable: %s setSuspendable: %s)", className, name, desc, susp, susp, setSuspendable);
                    }
                    classEntry.set(name, desc, susp);

                    if(susp == SuspendableType.SUSPENDABLE && (access & Opcodes.ACC_ABSTRACT) == 0){
                        if((access & Opcodes.ACC_SYNCHRONIZED) > 0) {
                            if (!db.isAllowMonitors()) {
                                throw new UnableToInstrumentException("synchronization is not support", className, name, desc);
                            } else
                                db.log(LogLevel.WARNING, "Method %s#%s%s is synchronized method which may cause block", className, name, desc);
                        }
                    }else {
                        MethodVisitor _mv = makeOutMV(mn);
                        // 一个 JSRInlinerAdapter 就是一个 methodenode, 只是覆写扩展了 visitJumpInsn visitEnd方法
                        // 这里又 屏蔽 visitEnd 的方法,
                        // 意思是通过返回值 将 mv 传出去在外层调用 visitEnd()
                        _mv = new JSRInlinerAdapter(_mv, access, name, desc, signature, exceptions);
                        // 此时默认的 mn 已经处理的差不多了
                        mn.accept(new MethodVisitor(ASMAPI, _mv) {
                            @Override
                            public void visitEnd() {
                                // don't call visitEnd on MV
                            }
                        }); // write method as-is 没看懂
                        this.mv = _mv;
                    }

                    commited = true;
                }

                @Override
                public AnnotationVisitor visitAnnotation(String s, boolean b) {
                    if(s.equals(SUSPENDABLE_DESC))
                        susp = SuspendableType.SUSPENDABLE;
                    else if(s.equals(DONT_INSTRUMENT_DESC))
                        susp = SuspendableType.NON_SUSPENDABLE;

                    susp = suspendableToSuperIfAbstract(access, susp);
                    return super.visitAnnotation(s, b);
                }

                @Override
                public void visitCode() {
                    commit(); // 这个地方为什么要 commit 一次呢
                    super.visitCode();
                }

                @Override
                public void visitEnd() {
                    if(exception != null) return;

                    commit();
                    try {
                        // 此处发生可能的 JSRInlinerAdapter => this.mv => this.mv.visitEnd()
                        super.visitEnd();
                    }catch (RuntimeException e){
                        exception = e;
                    }
                }
            };
        }

        return super.visitMethod(access, name, desc, signature, exceptions);
    }

    /**
     * 如果该方法没有具体实现 只是一个抽象函数 或者 接口函数, 则将其标注为 suspendable_super
     * @param access
     * @param suspendableType
     * @return
     */
    static SuspendableType suspendableToSuperIfAbstract(int access, SuspendableType suspendableType){
        if(suspendableType == SuspendableType.SUSPENDABLE
            && ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0))
            return SuspendableType.SUSPENDABLE_SUPER;
        return suspendableType;
    }

    private MethodVisitor makeOutMV(MethodNode mn){
        return super.visitMethod(mn.access, mn.name, mn.desc, mn.signature, toStringArray(mn.exceptions));
    }

    private static String[] toStringArray(List<?> l){
        if(l.isEmpty()) return null;
        return ((List<String>)l).toArray(new String[l.size()]);
    }

    private static SuspendableType max(SuspendableType a, SuspendableType b, SuspendableType defaultTypeIfNull){
        final SuspendableType res = max(a, b);
        return res != null ? res : defaultTypeIfNull;
    }

    private static SuspendableType max(SuspendableType a, SuspendableType b){
        if(a == null) return b;
        if(b == null) return a;
        return b.compareTo(a) > 0 ? b : a;
    }

    @Override
    public void visitEnd(){
        if(exception != null) throw exception;

        classEntry.setRequiresInstrumentation(false);
        db.recordSuspendableMethods(className, classEntry);

        if(methods != null && !methods.isEmpty()){
            if(alreadyInstrumented && !forceInstrumentation){
                for(MethodNode mn : methods){
                    db.log(LogLevel.INFO, "Already instrumented and not forcing, so not touching method %s#%s%s", className, mn.name, mn.desc);
                    mn.accept(makeOutMV(mn));
                }
            }else {
                if(!alreadyInstrumented){
                    emitInstrumentedAnn();
                    classEntry.setInstrumented(true);
                }

                for (MethodNode mn : methods){
                    final MethodVisitor outMV = makeOutMV(mn);
                    try {
                        InstrumentMethod im = new InstrumentMethod(db, sourceName, className, mn);
                        db.log(LogLevel.DEBUG, "About to instrument method %s#%s%s", className, mn.name, mn.desc);
                        im.accept(outMV, hasAnnotation(mn));
                    }catch (UnableToInstrumentException e){
                        db.log(LogLevel.WARNING, "UnableToInstrumentException encountered when instrumenting %s#%s%s: %s",
                                className, mn.name, mn.desc, e.getMessage());
                        mn.accept(outMV);
                    } catch (AnalyzerException e) {
                        e.printStackTrace();
                        throw new InternalError(e.getMessage());
                    }
                }
            }

        }
    }

    private boolean hasAnnotation(MethodNode mn) {
        final List<AnnotationNode> ans = mn.visibleAnnotations;
        if(ans == null) return false;
        for(AnnotationNode an : ans){
            if(an.desc.equals(SUSPENDABLE_DESC)) return true;
        }
        return false;
    }

    private void emitInstrumentedAnn() {
        final AnnotationVisitor instrumentedAV = visitAnnotation(INSTRUMENTED_DESC, true);
        instrumentedAV.visitEnd();
    }
}
